package com.zf.transaction.service;

import com.zf.transaction.entity.Student;
import com.zf.transaction.mapper.StudentMapper;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.cglib.proxy.MethodProxy;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.interceptor.AbstractFallbackTransactionAttributeSource;
import org.springframework.transaction.interceptor.TransactionInterceptor;
import org.springframework.transaction.support.TransactionSynchronization;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.springframework.transaction.support.TransactionSynchronizationUtils;
import org.springframework.transaction.support.TransactionTemplate;

import java.lang.reflect.Method;
import java.time.LocalDateTime;

/**
 * <pre>
 * Spring事务：
 *  1、声明式事务：@Transactional
 *  2、编程式事务：PlatformTransactionManager、TransactionTemplate
 *
 * Spring 声明式事务 @Transactional 失效的一些场景：
 *      1、目标方法不是public，方法无法被代理，原因见 {@link AbstractFallbackTransactionAttributeSource#computeTransactionAttribute(Method, Class)}。虽然Spring事务无效但是不会报错。
 *      2、propagation 属性设置有问题，导致以非事务方式执行。
 *      3、rollbackFor 属性设置的异常类型，不能覆盖抛出的异常，导致回滚失效。
 *      4、同个类中的自调用：非@Transactional方法 调用 @Transactional方法，此时 @Transactional方法不会使用Spring事务，因为this只指向的是原始对象，非AOP生成的代理对象，没有创建、提交/回滚事务的增强逻辑。
 *      5、方法内部捕捉了异常，没有抛出，导致回滚失效。
 *      6、数据库不支持事务。
 * </pre>
 */
@Slf4j
@Service
public class TransactionTestService {
    @Resource
    private ApplicationContext applicationContext;

    /**
     * 注入当前类被AOP代理后生成的代理对象, 添加 @Lazy 注解，防止循环依赖错误
     */
    @Lazy
    @Resource
    private TransactionTestService transactionTestService;

    @Resource
    private StudentMapper studentMapper;

    /**
     * 事务管理器：可以用于“手动创建、提交/回滚事务”
     */
    @Resource
    private PlatformTransactionManager platformTransactionManager;
    /**
     * 事务模板，封装了 PlatformTransactionManager，只需要提供业务代码即可，“事务的创建、提交/回滚”会自动按照模板完成。
     */
    @Resource
    private TransactionTemplate transactionTemplate;

    /**
     * <pre>
     * 自调用时，Spring事务失效问题：
     * 解决方式：使用生成的代理类去调用：
     *      1、ApplicationContext.getBean() 获取当前类被AOP代理后生成的代理对象
     *      2、注入当前类被AOP代理后生成的代理对象
     *      3、AopContext.currentProxy() 获取当前类被AOP代理后生成的代理对象，需要 1、引入spring-boot-starter-aop；2、添加 @EnableAspectJAutoProxy(exposeProxy = true)
     * </pre>
     */
    public Student selfInvoke() throws Exception {
        // 直接调用，this是被代理的对象，无法执行事务相关逻辑，Spring事务会失效
        // Student student = this.declareTransactionTest();


        // 1、从bean工厂中获取当前类被AOP代理后生成的代理对象，然后调用事务方法，Spring事务生效
        // TransactionTestService transactionTestServiceAc = applicationContext.getBean(TransactionTestService.class);
        // System.out.println(transactionTestServiceAc.getClass());
        // Student student = transactionTestServiceAc.declareTransactionTest();

        // 2、注入当前类被AOP代理后生成的代理对象
        System.out.println(transactionTestService.getClass());
        Student student = transactionTestService.declareTransactionTest();

        // AopContext.currentProxy() 获取当前类被Aop代理后生成的对象
        // TransactionTestService currentProxy = (TransactionTestService) AopContext.currentProxy();
        // System.out.println(currentProxy.getClass());
        // Student student = currentProxy.declareTransactionTest();

        return student;
    }

    /**
     * <pre>
     * 声明式事务样例:
     * 大致步骤：
     * 1、Spring对包含了@Transactional注解的类创建代理对象，
     * 2、在代理对象的方法中执行拦截器 {@link org.springframework.aop.framework.CglibAopProxy.DynamicAdvisedInterceptor#intercept(Object, Method, Object[], MethodProxy)} 方法,
     * 3、在2中获取并执行所有的 {@link MethodInterceptor#invoke(MethodInvocation)}。
     *             其中就包括事务拦截器 {@link TransactionInterceptor#invoke(MethodInvocation)}，在这个方法中完成了“在原始方法前后执行 创建、提交/回滚事务”的操作。
     *
     * Transactional注解中的默认使用的事务管理器是 TransactionManager bean:
     *      传统的是: org.springframework.transaction.PlatformTransactionManager,
     *      响应式的是：org.springframework.transaction.ReactiveTransactionManager
     * </pre>
     */
    @Transactional(rollbackFor = {Exception.class})
    public Student declareTransactionTest() throws Exception {

        // 判断当前线程是否有实际的事务正在运行
        boolean actualTransactionActive = TransactionSynchronizationManager.isActualTransactionActive();
        System.out.println(actualTransactionActive);

        // 判断当前线程是否启用了事务同步机制，事务同步是指在事务的不同阶段（如提交、回滚）执行回调的能力。即使没有实际的事务，事务同步也可能被激活
        if (TransactionSynchronizationManager.isSynchronizationActive()) {
            // 给当前线程上的事务 注册一个事务同步器，在 事务提交/回滚 前后 执行一些操作，由 org.springframework.transaction.support.TransactionSynchronizationUtils 类执行
            TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
                /**
                 * <pre>
                 * 此方法在 事务执行“提交操作”前 被调用，调用见 {@link TransactionSynchronizationUtils#triggerBeforeCommit(boolean)}。
                 *
                 * 注意：如果“事务没有执行提交操作”，则不调用该方法。
                 *
                 * 如果此方法异常，异常会传播给调用者，导致事务回滚。
                 *
                 * 此方法在 {@link TransactionSynchronization#beforeCompletion()} 方法前被调用。
                 * </pre>
                 *
                 * @param readOnly 事务是否被定义成只读
                 */
                @Override
                public void beforeCommit(boolean readOnly) {
                    // throw new RuntimeException("beforeCommit: exp");
                    System.out.println("beforeCommit: " + readOnly);
                }

                /**
                 * <pre>
                 * 此方法在 事务执行"提交/回滚操作"之前 被调用，调用见 {@link TransactionSynchronizationUtils#triggerBeforeCompletion()}。
                 *
                 * 此方法无论是否抛出异常，都不会传播给调用者，仅仅只捕捉异常并打印异常信息，不会对事务的提交/回滚产生影响。
                 *
                 * 此方法在 {@link TransactionSynchronization#beforeCommit(boolean)} 方法之后调用，即使beforeCommit(boolean)发生异常也会继续调用该方法。
                 * </pre>
                 */
                @Override
                public void beforeCompletion() {
                    // throw new RuntimeException("beforeCompletion: exp");
                    System.out.println("beforeCompletion");
                }

                /**
                 * <pre>
                 * 此方法在 "成功提交事务"后 被调用。可以在 "成功提交事务"后 立即执行进一步操作，例如消息或电子邮件。
                 * 调用见 {@link TransactionSynchronizationUtils#triggerAfterCommit()}。
                 *
                 * 此方法抛出异常会传播给调用者，但不会导致事务回滚，因为事务已经被成功提交；只是异常会被进一步抛出，导致后续代码的执行被中断。
                 * </pre>
                 */
                @Override
                public void afterCommit() {
                    // throw new RuntimeException("afterCommit: exp");
                    System.out.println("afterCommit");
                }

                /**
                 * <pre>
                 * 此方法在 “完成事务提交/回滚”后 被调用。可以在 事务结束后 执行一些操作，例如消息、电子邮件、资源清理等。
                 * 调用见 {@link TransactionSynchronizationUtils#triggerAfterCompletion(int)}。
                 *
                 * 此方法无论是否抛出异常，都不会传播给调用者，仅仅只捕捉异常并打印异常信息，不会对事务的提交/回滚产生影响，因为事务已经完成了“提交/回滚”。
                 * </pre>
                 *
                 * @param status 事务的状态，见 {@link TransactionSynchronization#STATUS_COMMITTED},
                 *                          {@link TransactionSynchronization#STATUS_ROLLED_BACK},
                 *                          {@link TransactionSynchronization#STATUS_UNKNOWN}
                 */
                @Override
                public void afterCompletion(int status) {
                    // throw new RuntimeException("afterCompletion: exp");

                    String statusName = "";
                    // 事务的状态，提交/回滚/未知
                    if (TransactionSynchronization.STATUS_COMMITTED == status) {
                        // 提交 状态
                        statusName = "STATUS_COMMITTED";
                    } else if (TransactionSynchronization.STATUS_ROLLED_BACK == status) {
                        // 回滚 状态
                        statusName = "STATUS_ROLLED_BACK";
                    } else {
                        // 未知 状态
                        statusName = "STATUS_UNKNOWN";
                    }
                    System.out.println("afterCompletion：" + statusName);
                }

            });
        }

        // 业务代码
        // 创建对象
        Student stu = new Student();
        stu.setId(1);
        stu.setName("张三");
        stu.setAge(22);
        stu.setGender(1);
        stu.setGrade("高三");
        stu.setSchool("二中");
        stu.setClassNo("2班");
        stu.setCreatedTime(LocalDateTime.now());
        stu.setUpdatedTime(LocalDateTime.now());

        // 插入数据库
        studentMapper.insert(stu);

        // 1、可以抛出异常，来回滚事务

        // 2、也可以不抛出异常，设置回滚标识，也能回滚事务
        // if (actualTransactionActive) {
        //     TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
        // }

        return stu;
    }


    /**
     * 使用 TransactionTemplate 完成编程式事务
     */
    public Student programTransactionByTransactionTemplate() {
        // 执行一个不需要返回值的事务操作，在方法执行的前后自动完成事务的创建、提交/回滚操作
        // transactionTemplate.executeWithoutResult(transactionStatus -> {
        //     // 事务操作
        // });

        // 执行一个需要返回值的事务操作，在方法执行的前后自动完成事务的创建、提交/回滚操作
        Student res = transactionTemplate.execute(transactionStatus -> {
            // 业务代码
            // 创建对象
            Student stu = new Student();
            stu.setId(2);
            stu.setName("李四");
            stu.setAge(33);
            stu.setGender(1);
            stu.setGrade("大五");
            stu.setSchool("五道口");
            stu.setClassNo("4班");
            stu.setCreatedTime(LocalDateTime.now());
            stu.setUpdatedTime(LocalDateTime.now());

            // 插入数据库
            studentMapper.insert(stu);

            // 1、如果抛出异常则自动回滚，如果正常则自动提交

            // 2、不抛出异常，手动设置回滚，打断模板的正常提交操作，也可以将提交操作变成回滚
            // transactionStatus.setRollbackOnly();

            return stu;
        });

        // 如果事务成功提交，则可以执行后续操作，
        // 如果事务因为异常回滚，则异常被抛出，代码执行被中断，
        System.out.println("后续操作");

        return res;
    }

    /**
     * 使用 PlatformTransactionManager 完成编程式事务
     */
    public Student programTransactionByPlatformTransactionManager() {
        // 创建事务定义，可以设置一些定义配置，一般直接使用默认值即可
        // DefaultTransactionDefinition transactionDefinition = new DefaultTransactionDefinition();

        // 获取当前已经存在的活跃事务，或创建一个新的事务：传入null则使用默认定义 TransactionDefinition.withDefaults()
        TransactionStatus transactionStatus = platformTransactionManager.getTransaction(null);
        // 执行业务操作
        try {
            // 业务代码
            // 创建对象
            Student stu = new Student();
            stu.setId(3);
            stu.setName("王五");
            stu.setAge(24);
            stu.setGender(1);
            stu.setGrade("大九");
            stu.setSchool("五道口");
            stu.setClassNo("6班");
            stu.setCreatedTime(LocalDateTime.now());
            stu.setUpdatedTime(LocalDateTime.now());

            // 插入数据库
            studentMapper.insert(stu);

            // 通过抛出异常来回滚事务

            // 提交事务
            platformTransactionManager.commit(transactionStatus);
            return stu;
        } catch (Exception ex) {
            // 事务是否已经完成，即是否已经完成了提交/回滚，完成后不能再进行提交/回滚操作
            if (!transactionStatus.isCompleted()) {
                // 回滚事务
                platformTransactionManager.rollback(transactionStatus);
            }
            // 打印异常信息，自行决定是否继续抛出异常
            log.error("Exception rollback", ex);
            return null;
        }
    }

}
